
/* GCSx
** WORLD.CPP
**
** World storage
** Non-editor functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

World::World() : title("World"), scenesById(), scenesByName(), tilesetsById(), tilesetsByName(), animgroupsById(), animgroupsByName(), scriptsById(), scriptsByNameCode(), scriptsByNameLib(), scriptsByNameNote(), globalLinks() { start_func
    filename = NULL;
    file = NULL;
    startingScene = 0;
    globalLinksDone = 0;
    globalLinksCount = 0;
}

World::World(const char* wFilename) throw_File : title(blankString), scenesById(), scenesByName(), tilesetsById(), tilesetsByName(), animgroupsById(), animgroupsByName(), scriptsById(), scriptsByNameCode(), scriptsByNameLib(), scriptsByNameNote(), globalLinks() { start_func
    filename = NULL;
    file = NULL;
    startingScene = 0;
    globalLinksDone = 0;
    globalLinksCount = 0;
    
    TileSet* tileset = NULL;
    Scene* scene = NULL;
    AnimGroup* animgroup = NULL;
    Script* script = NULL;
    
    try {
        filename = new string(wFilename);
        file = new WorldFileLoad(filename);

        Uint32 type;
        Uint32 id;
        
        // Loop pass 1- World Info, TileSets
        file->scanBlocks();
        while (file->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_WORLDINFO:
                    file->claimBlock(id, this);
                    break;
                    
                case WorldFileLoad::BLOCKTYPE_TILESET:
                    tileset = new TileSet(this);
                    file->claimBlock(id, tileset);
                    indexTileSet(tileset);
                    tileset = NULL;
                    break;
            }
        }

        // Loop pass 2- Animation Groups (need tilesets)
        file->scanBlocks();
        while (file->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_ANIMGROUP:
                    animgroup = new AnimGroup(this);
                    file->claimBlock(id, animgroup);
                    indexAnimGroup(animgroup);
                    animgroup = NULL;
                    break;
            }
        }

        // Loop pass 3- Scripts (need tilesets/animgroups)
        file->scanBlocks();
        while (file->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_SCRIPT:
                    script = new Script(this);
                    file->claimBlock(id, script);
                    indexScript(script);
                    script = NULL;
                    break;
            }
        }

        // Loop pass 4- Scenes (need tilesets/animgroups/scripts for spawns)
        file->scanBlocks();
        while (file->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_SCENE:
                    scene = new Scene(this);
                    file->claimBlock(id, scene);
                    indexScene(scene);
                    scene = NULL;
                    break;
            }
        }
    }
    catch (...) {
        // Temporaries
        delete tileset;
        delete scene;
        delete animgroup;
        delete script;
        
        // Collections
        deleteCollections();    
        
        // File
        delete filename;
        delete file;
        filename = NULL;
        file = NULL;
        throw;
    }
}

World::~World() { start_func
    clearGlobalLinks();
    // Tilesets, etc.
    deleteCollections();    
    
    // Close world file
    delete file;
    delete filename;
}
    
void World::clearGlobalLinks() { start_func
    GlobalMap::iterator pos = globalLinks.begin();
    while (pos != globalLinks.end()) {
        delete[] (*pos).second.second;
        const char* toDel = (*pos).first;
        globalLinks.erase(pos);
        delete[] toDel;
        pos = globalLinks.begin();
    }
    
    globalLinksDone = 0;
    globalLinksCount = 0;
}

void World::deleteCollections() { start_func
    // Delete scenes (and layers/spawns)
    SceneIndex::iterator posS;
    SceneIndex::iterator endS = scenesById.end();
    for (posS = scenesById.begin(); posS != endS; ++posS) {
        delete (*posS).second;
        (*posS).second = NULL;
    }
    
    // Delete anim groups
    AnimGroupIndex::iterator posA;
    AnimGroupIndex::iterator endA = animgroupsById.end();
    for (posA = animgroupsById.begin(); posA != endA; ++posA) {
        delete (*posA).second;
        (*posA).second = NULL;
    }

    // Scenes and Anim Groups depend on tilesets
    // Delete tilesets
    TileSetIndex::iterator posT;
    TileSetIndex::iterator endT = tilesetsById.end();
    for (posT = tilesetsById.begin(); posT != endT; ++posT) {
        delete (*posT).second;
        (*posT).second = NULL;
    }
    
    // Many things depend on scripts
    // Delete scripts
    ScriptIndex::iterator posX;
    ScriptIndex::iterator endX = scriptsById.end();
    for (posX = scriptsById.begin(); posX != endX; ++posX) {
        delete (*posX).second;
        (*posX).second = NULL;
    }
}

void World::loadHeader(FileRead* file) throw_File { start_func
    if (file->getVersion() > 1) {
        throw FileException("Unsupported world header version %d", file->getVersion());
    }

    file->readStr(title);
    startingScene = file->readInt();
    
    clearGlobalLinks();
    globalLinksCount = file->readInt();
    if (globalLinksCount >= 0) {
        int count = globalLinksCount;
        set<int> posUsed;
        while (count--) {
            string name;
            string wart;
            
            file->readStr(name);
            int pos = file->readInt();
            file->readStr(wart);
            
            // (ensure globals aren't duplicated in name or position)
            if ((pos < 0) || (pos >= globalLinksCount) ||
                (globalLinks.find(name.c_str()) != globalLinks.end()) ||
                (posUsed.find(pos) != posUsed.end()))
                throw FileException("Corrupted global variables");
                
            // (ensure name contains a valid wart)
            DataType dt;
            const char* endWart = wartToDataType(wart.c_str(), dt);
            if ((!endWart) || (*endWart != 0) || (dt.baseType == DATA_VOID))
                throw FileException("Corrupted global variables");
            
            const char* nameC = newCpCopy(name);
            globalLinks[nameC].first = pos;
            globalLinks[nameC].second = newCpCopy(wart);
            posUsed.insert(pos);
        }
        globalLinksDone = 1;
    }
    else if (globalLinksCount == -1) {
        globalLinksCount = 0;
    }
    else {
        throw FileException("Corrupted world header");
    }
}

void World::loadContent(FileRead* file) { start_func
    // (no content; worldedit may override this)
    delete file;
}

int World::isContentCached() const { start_func
    // (no content; worldedit doesn't cache content)
    return 0;
}

void World::cacheLoad() { start_func
    // (no content; worldedit doesn't cache content)
}

int World::markLock() { start_func
    // (no content to lock)
    return 0;
}

int World::markUnlock() { start_func
    // (no content to lock)
    return 0;
}

int World::isLocked() const { start_func
    // (no content, no lock)
    return 0;
}

void World::indexScene(Scene* addScene) { start_func
    assert(addScene);
    assert(scenesByName.find(addScene->getNameL().c_str()) == scenesByName.end());
    assert(scenesById.find(addScene->getId()) == scenesById.end());

    scenesByName.insert(pair<const char*, Scene*>(addScene->getNameL().c_str(), addScene));
    scenesById.insert(pair<int, Scene*>(addScene->getId(), addScene));
}

void World::deindexScene(Scene* remScene) { start_func
    assert(remScene);
    scenesByName.erase(remScene->getNameL().c_str());
    scenesById.erase(remScene->getId());
}

Scene* World::findScene(const string& fName) const { start_func
    SceneMap::const_iterator loc = scenesByName.find(fName.c_str());
    if (loc == scenesByName.end()) return NULL;
    return (*loc).second;
}

Scene* World::findScene(int fId) const { start_func
    SceneIndex::const_iterator loc = scenesById.find(fId);
    if (loc == scenesById.end()) return NULL;
    return (*loc).second;
}

void World::indexTileSet(TileSet* addTileSet) { start_func
    assert(addTileSet);
    assert(tilesetsByName.find(addTileSet->getNameL().c_str()) == tilesetsByName.end());
    assert(tilesetsById.find(addTileSet->getId()) == tilesetsById.end());

    tilesetsByName.insert(pair<const char*, TileSet*>(addTileSet->getNameL().c_str(), addTileSet));
    tilesetsById.insert(pair<int, TileSet*>(addTileSet->getId(), addTileSet));
}

void World::deindexTileSet(TileSet* remTileSet) { start_func
    assert(remTileSet);
    tilesetsByName.erase(remTileSet->getNameL().c_str());
    tilesetsById.erase(remTileSet->getId());
}

TileSet* World::findTileSet(const string& fName) const { start_func
    TileSetMap::const_iterator loc = tilesetsByName.find(fName.c_str());
    if (loc == tilesetsByName.end()) return NULL;
    return (*loc).second;
}

TileSet* World::findTileSet(int fId) const { start_func
    TileSetIndex::const_iterator loc = tilesetsById.find(fId);
    if (loc == tilesetsById.end()) return NULL;
    return (*loc).second;
}

void World::indexAnimGroup(AnimGroup* addAnimGroup) { start_func
    assert(addAnimGroup);
    assert(animgroupsByName.find(addAnimGroup->getNameL().c_str()) == animgroupsByName.end());
    assert(animgroupsById.find(addAnimGroup->getId()) == animgroupsById.end());

    animgroupsByName.insert(pair<const char*, AnimGroup*>(addAnimGroup->getNameL().c_str(), addAnimGroup));
    animgroupsById.insert(pair<int, AnimGroup*>(addAnimGroup->getId(), addAnimGroup));
}

void World::deindexAnimGroup(AnimGroup* remAnimGroup) { start_func
    assert(remAnimGroup);
    animgroupsByName.erase(remAnimGroup->getNameL().c_str());
    animgroupsById.erase(remAnimGroup->getId());
}

AnimGroup* World::findAnimGroup(const string& fName) const { start_func
    AnimGroupMap::const_iterator loc = animgroupsByName.find(fName.c_str());
    if (loc == animgroupsByName.end()) return NULL;
    return (*loc).second;
}

AnimGroup* World::findAnimGroup(int fId) const { start_func
    AnimGroupIndex::const_iterator loc = animgroupsById.find(fId);
    if (loc == animgroupsById.end()) return NULL;
    return (*loc).second;
}

void World::indexScript(Script* addScript) { start_func
    assert(addScript);
    if (addScript->getType() == Script::SCRIPT_CODE) {
        assert(scriptsByNameCode.find(addScript->getNameL().c_str()) == scriptsByNameCode.end());
        scriptsByNameCode.insert(pair<const char*, Script*>(addScript->getNameL().c_str(), addScript));
    }
    else if (addScript->getType() == Script::SCRIPT_LIBRARY) {
        assert(scriptsByNameLib.find(addScript->getNameL().c_str()) == scriptsByNameLib.end());
        scriptsByNameLib.insert(pair<const char*, Script*>(addScript->getNameL().c_str(), addScript));
    }
    else {
        assert(addScript->getType() == Script::SCRIPT_NOTE);
        assert(scriptsByNameNote.find(addScript->getNameL().c_str()) == scriptsByNameNote.end());
        scriptsByNameNote.insert(pair<const char*, Script*>(addScript->getNameL().c_str(), addScript));
    }

    assert(scriptsById.find(addScript->getId()) == scriptsById.end());
    scriptsById.insert(pair<int, Script*>(addScript->getId(), addScript));
}

void World::deindexScript(Script* remScript) { start_func
    assert(remScript);
    if (remScript->getType() == Script::SCRIPT_CODE) {
        scriptsByNameCode.erase(remScript->getNameL().c_str());
    }
    else if (remScript->getType() == Script::SCRIPT_LIBRARY) {
        scriptsByNameLib.erase(remScript->getNameL().c_str());
    }
    else {
        assert(remScript->getType() == Script::SCRIPT_NOTE);
        scriptsByNameNote.erase(remScript->getNameL().c_str());
    }
    scriptsById.erase(remScript->getId());
}

Script* World::findScriptCode(const string& fName) const { start_func
    ScriptMap::const_iterator loc = scriptsByNameCode.find(fName.c_str());
    if (loc == scriptsByNameCode.end()) return NULL;
    return (*loc).second;
}

Script* World::findScriptLib(const string& fName) const { start_func
    ScriptMap::const_iterator loc = scriptsByNameLib.find(fName.c_str());
    if (loc == scriptsByNameLib.end()) return NULL;
    return (*loc).second;
}

Script* World::findScriptNote(const string& fName) const { start_func
    ScriptMap::const_iterator loc = scriptsByNameNote.find(fName.c_str());
    if (loc == scriptsByNameNote.end()) return NULL;
    return (*loc).second;
}

Script* World::findScript(int fId) const { start_func
    ScriptIndex::const_iterator loc = scriptsById.find(fId);
    if (loc == scriptsById.end()) return NULL;
    return (*loc).second;
}

int World::buildScripts() { start_func
    // Determine what to build (to prevent locking unnecessarily)
    vector<Script*> toBuild;
    ScriptIndex::iterator pos;
    ScriptIndex::iterator end = scriptsById.end();
    for (pos = scriptsById.begin(); pos != end; ++pos) {
        if (((*pos).second->getType() != Script::SCRIPT_NOTE) &&
            ((*pos).second->status() < Script::CODE_COMPILED)) {
            (*pos).second->markLock();
            toBuild.push_back((*pos).second);
        }
    }
    
    // toBuild is all locked- now parse all (anything not in toBuild
    // is already parsed and that parsed function list is already loaded anyway)
    vector<Script*>::iterator posB;
    vector<Script*>::iterator endB = toBuild.end();
    for (posB = toBuild.begin(); posB != endB; ++posB) {
        (*posB)->parseFuncs();
    }
    // Compile all
    int errors = 0;
    for (posB = toBuild.begin(); posB != endB; ++posB) {
        (*posB)->compile();
        errors += (*posB)->numErrors();
    }
    // Unlock all
    for (posB = toBuild.begin(); posB != endB; ++posB) {
        (*posB)->markUnlock();
    }
    
    // Generate global variable list
    if (!errors) {
        // only generate if 1) we compiled something or
        // 2) has never been generated
        if ((!globalLinksDone) || (!toBuild.empty()))
            errors = prepGlobalLinks();
    }

    return errors;
}

int World::prepGlobalLinks() { start_func
    int errors = 0;
    clearGlobalLinks();
    
    // Scan ALL link lists for LINK_GLOBAL(VAR)
    ScriptIndex::iterator pos;
    ScriptIndex::iterator end = scriptsById.end();
    for (pos = scriptsById.begin(); pos != end; ++pos) {
        Script* script = (*pos).second;
        if (script->getType() != Script::SCRIPT_NOTE) {
            assert(script->status() >= Script::CODE_COMPILED);
            assert(script->numErrors() == 0);
            
            script->collectGlobalLinks(&globalLinks, &globalLinksCount);
            errors += script->numErrors();
            
            assert(globalLinksCount == (int)globalLinks.size());
        }
    }
    
    if (!errors) globalLinksDone = 1;
    
    return errors;
}
